Skip to content

fix(voice): guard AgentTask handoff run state#1961

Merged
toubatbrian merged 3 commits into
mainfrom
feasible-raved-preppier
Jul 7, 2026
Merged

fix(voice): guard AgentTask handoff run state#1961
toubatbrian merged 3 commits into
mainfrom
feasible-raved-preppier

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Testing

  • pnpm test -- agents/src/voice/agent.test.ts agents/src/voice/testing/run_result.test.ts
  • pnpm build:agents
  • cue-cli text-mode runtime validation with a temporary dispatchable AgentTask worker, asserting handoff and finish tool framework events (not committed)

Ported from livekit/agents#6315

Original PR description

fix livekit/agents#6313

Problem

An agent handoff triggered by a speech that predates the active run is not watched by session.run()'s RunResult. For example, when AgentSession.start() runs on_enter and its reply calls a tool that awaits an AgentTask, nothing watches the tasks driving that handoff. The first session.run() then completes as soon as its own reply finishes — while the handoff is still mid-transition (old activity paused, new activity not started) — and the next session.run() races the handoff and fails with RuntimeError: cannot schedule new speech, the speech scheduling is draining/pausing (a hang on 1.6.1).

This can be worked around with await session.start(agent, capture_run=True): the captured run exists before on_enter speaks, so the on_enter speech is watched and start() waits for the handoff chain to settle before returning.

1.6.0 has the same bug but wins another race: the run still completes mid-handoff, but the next session.run()'s speech lands before the old activity is paused, so it is queued on the old activity and answered by the outgoing agent.

Fix

AgentTask.__await_impl already assumes the tasks driving the handoff (blocked_tasks) are watched by the active run — it unwatches them right after installing the new activity's on_enter guard. This PR adds them to the run's watch list when the handoff starts, so the run cannot complete mid-handoff; they are unwatched once the handoff settles as before, so the run does not stay blocked on the long-lived tool task.

Added a regression test where the handoff is triggered from a pre-run on_enter speech.

@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 27bd059

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 35 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@rosetta-livekit-bot rosetta-livekit-bot Bot requested a review from longcw July 6, 2026 01:50

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Open in Devin Review

Comment thread agents/src/voice/agent.ts Outdated
Comment thread agents/src/voice/agent.ts
Ports test_handoff_from_pre_run_speech from livekit/agents#6315: a handoff
triggered by an onEnter speech that predates session.run() must keep the
run alive until the activity transition settles. Without the fix in this
PR, run() stalls mid-handoff and the test times out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@toubatbrian toubatbrian requested a review from a team as a code owner July 7, 2026 04:08

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread agents/src/voice/agent.ts
Comment thread agents/src/voice/agent.ts
…eset

Match the Python original's `if suspended_handles:` guard: a run created
mid-transition has no watched handles yet (its generateReply is deferred
behind the activity lock), so calling _markDoneIfNeeded on an empty handle
set would resolve it before it produced any events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@toubatbrian toubatbrian merged commit 36e6460 into main Jul 7, 2026
9 checks passed
@toubatbrian toubatbrian deleted the feasible-raved-preppier branch July 7, 2026 07:49
@github-actions github-actions Bot mentioned this pull request Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

awaiting nested AgentTask in a tool call within parent Agent's on_enter() stops working since v1.6.1

1 participant